#include "Buffer.h"

#include <errno.h>
#include <sys/uio.h>
#include <unistd.h>

/**
 * 从fd上读取数据：Muduo Poller工作在LT模式下，只要socket fd上有数据就会一直通知
 * Buffer缓冲区是有大小的，但是从fd上读数据，却不知道tcp数据最终的大小
 * readFd安全高效地将fd上数据读入到Buffer缓冲区，避免缓冲区数据丢失和频繁扩容
 * 通过分散读readv系统调用实现
 */
ssize_t Buffer::readFd(int fd, int *saveErrno)
{
    char extrabuf[65535] = {0}; // 栈上的内存空间，临时缓冲区。大小：64kb

    struct iovec vec[2]; // 用于存放两个缓冲区信息

    const size_t writable = writableBytes(); // 底层Buffer缓冲区剩余的可写空间的大小

    // 第一个缓冲区：Buffer内部的可写区域
    vec[0].iov_base = begin() + writerIndex_;
    vec[0].iov_len = writable;

    // 第二个缓冲区：栈上的extrabuf
    vec[1].iov_base = extrabuf;
    vec[1].iov_len = sizeof(extrabuf);

    // 决定使用几个缓冲区
    // 如果Buffer的可写缓冲区小于65535字节，可能不够存，所以用两个缓冲区，确保数据不会丢失
    // 如果Buffer的可写缓冲区大于65535字节，单个缓冲区足够，只用Buffer自身的缓冲区，避免不必要的栈空间使用
    const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;

    // 分散读，按顺序从第一个缓冲区开始填充数据
    const ssize_t n = ::readv(fd, vec, iovcnt);
    if (n < 0)
    {
        *saveErrno = errno;
    }
    else if (n <= writable) // Buffer的可写缓冲区已经够存储读出来的数据了，数据全部存入Buffer自身
    {
        writerIndex_ += n;
    }
    else // extrabuf也写了数据，说明Buffer自身空间不足，vec[0]写满之后，剩余数据存入extrabuf
    {
        writerIndex_ = buffer_.size();  // 写指针移动到末尾
        append(extrabuf, n - writable); // 追加extrabuf中的剩余数据到Buffer中。append会自动扩容，writerIndex_开始写n-writable大小的数据
    }
    return n;
}

ssize_t Buffer::writeFd(int fd, int *saveErrno)
{
    ssize_t n = ::write(fd, peek(), readableBytes());
    if (n < 0)
    {
        *saveErrno = errno;
    }
    return n;
}